home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / gst-0.10 / gst / extend / jukebox.py < prev    next >
Encoding:
Python Source  |  2009-02-21  |  11.3 KB  |  357 lines

  1. #!/usr/bin/env python
  2. # -*- Mode: Python -*-
  3. # vi:si:et:sw=4:sts=4:ts=4
  4. #
  5. # GStreamer python bindings
  6. # Copyright (C) 2005 Edward Hervey <edward at fluendo dot com>
  7. # Copyright (C) 2005 Thomas Vander Stichele <thomas at apestaart dot org>
  8.  
  9. # This library is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU Lesser General Public
  11. # License as published by the Free Software Foundation; either
  12. # version 2.1 of the License, or (at your option) any later version.
  13. #
  14. # This library is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17. # Lesser General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public
  20. # License along with this library; if not, write to the Free Software
  21. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  22.  
  23. import os
  24. import sys
  25. import pickle
  26. import random as rand
  27.  
  28. import gobject
  29. gobject.threads_init()
  30. import pygst
  31. pygst.require('0.10')
  32. import gst
  33.  
  34. import utils
  35. from pygobject import gsignal
  36. import sources
  37. from leveller import Leveller
  38.  
  39. class Jukebox(gst.Bin):
  40.     gsignal('done', str)
  41.     gsignal('prerolled')        # emitted when at least 2 sources are ready
  42.     gsignal('changed', str, gobject.TYPE_UINT64) # clocktime, filename
  43.     gsignal('looped')
  44.  
  45.     def __init__(self, files, rms=0.2, loops=0, random=False,
  46.                  caps="audio/x-raw-int,channels=2,rate=44100",
  47.                  picklepath='level.pck'):
  48.         # with pygtk 2.4 this call is needed for the gsignal to work
  49.         self.__gobject_init__()
  50.  
  51.         self._target_rms = rms
  52.         self._loopsleft = loops
  53.         self._loopsdone = 0
  54.         self._random = random
  55.         self._picklepath = picklepath
  56.         self._caps = gst.caps_from_string(caps)
  57.         self._files = files[:] # copy
  58.         self._levels = {} # filename -> rms, mixin, mixout, length
  59.         self._prerolled = False
  60.         self._playing = False
  61.         self._scani = 0 # index into self._files for scanning
  62.         self._playi = 0 # index into self._files for playing
  63.  
  64.         self._lastadded = None # last file added to composition
  65.         self._lastposition = long(0) # last position where file was added
  66.  
  67.         if not len(files) > 1:
  68.             raise TypeError, 'Must have at least 2 files'
  69.  
  70.         self._composition = gst.element_factory_make("gnlcomposition")
  71.         self._composition.connect('pad-added', self._composition_pad_added_cb)
  72.         self.add(self._composition)
  73.  
  74.         self._srcpad = None
  75.  
  76.         # load our pickle if it exists
  77.         if os.path.exists(self._picklepath):
  78.             file = open(self._picklepath)
  79.             self._levels = pickle.load(file)
  80.             file.close()
  81.  
  82.         # randomize our list if asked for
  83.         if self._random:
  84.             self._files = rand.sample(self._files, len(self._files))
  85.  
  86.  
  87.     ## public API
  88.  
  89.     def preroll(self):
  90.         # scan the first few files and start playing
  91.         gst.debug("starting jukebox prerolling")
  92.         self._scan()
  93.  
  94.     def start(self):
  95.         ##
  96.         ## FIXME : THIS SHOULD'T BE NEEDED !
  97.         ## USE STATE CHANGES INSTEAD
  98.         ## 
  99.         if not self._prerolled:
  100.             raise Exception, "baby"
  101.         self.set_state(gst.STATE_PAUSED)
  102.  
  103.  
  104.     ## Scanning private methods
  105.  
  106.     def _scan(self):
  107.         # start a leveller for a new _toscan file
  108.         if self._scani >= len(self._files):
  109.             gst.debug("We're done scanning !")
  110.             return
  111.  
  112.         file = self._files[self._scani]
  113.         self._scani += 1
  114.  
  115.         if file in self._levels.keys():
  116.             gst.debug("already did file %s" % file)
  117.             self._check_prerolled()
  118.             gobject.timeout_add(0, self._scan)
  119.             return
  120.  
  121.         gst.debug("creating leveller for %s" % file)
  122.         leveller = Leveller(file)
  123.         leveller.connect('done', self._leveller_done_cb, file)
  124.         gobject.timeout_add(0, leveller.start)
  125.         ##gobject.idle_add(leveller.iterate)
  126.  
  127.     def _leveller_done_cb(self, l, reason, file):
  128.         if reason != sources.EOS:
  129.             gst.debug("Error: %s" % reason)
  130.             return
  131.  
  132.         gst.debug("in: %s, out: %s" % (gst.TIME_ARGS(l.mixin),
  133.                                        gst.TIME_ARGS(l.mixout)))
  134.         gst.debug("rms: %f, %f dB" % (l.rms, l.rmsdB))
  135.  
  136.         # store infos
  137.         self._levels[file] = (l.rms, l.mixin, l.mixout, l.length)
  138.  
  139.         gst.debug("writing level pickle")
  140.         file = open(self._picklepath, "w")
  141.         pickle.dump(self._levels, file)
  142.         file.close()
  143.  
  144.         self._check_prerolled()
  145.         self._scan()
  146.  
  147.         # clean up leveller after this handler
  148.         gobject.timeout_add(0, l.clean)
  149.  
  150.  
  151.     ## GnlSource-related methods
  152.  
  153.     def _new_gnl_source(self, location, start):
  154.         """
  155.         Creates a new GnlSource containing an AudioSource with the following
  156.         properties correctly set:
  157.         _ volume level
  158.         _ priority
  159.         _ duration
  160.         The start position MUST be given
  161.         """
  162.         if not self._levels[location]:
  163.             return None
  164.         self.debug("Creating new GnlSource at %s for %s" % (gst.TIME_ARGS(start), location))
  165.         idx = self._files.index(location) + self._loopsdone * len(self._files)
  166.         rms, mixin, mixout, duration = self._levels[location]
  167.         gnls = gst.element_factory_make("gnlsource", "source-%d-%s" % (idx, location))
  168.         src = sources.AudioSource(location)
  169.         gnls.add(src)
  170.  
  171.         # set volume
  172.         level = 1.0
  173.         if rms > self._target_rms:
  174.             level = self._target_rms / rms
  175.             gst.debug('setting volume of %f' % level)
  176.         else:
  177.             gst.debug('not going to go above 1.0 level')
  178.         src.set_volume(level)
  179.  
  180.         # set proper position/duration/priority in composition
  181.         gnls.props.priority = (2 * self._loopsdone) + 1 + (idx % 2)
  182.         gnls.props.start = long(start)
  183.         gnls.props.duration = long(duration)
  184.         gnls.props.media_duration = long(duration)
  185.         gnls.props.media_start = long(0)
  186.  
  187.         return gnls
  188.  
  189.     def _new_mixer(self, start, duration):
  190.         gnlo = gst.element_factory_make("gnloperation")
  191.         ad = gst.element_factory_make("adder")
  192.         gnlo.add(ad)
  193.         gnlo.props.sinks = 2
  194.         gnlo.props.start = start
  195.         gnlo.props.duration = duration
  196.         gnlo.props.priority = 0
  197.  
  198.         return gnlo
  199.  
  200.     def _append_file(self, location):
  201.         """
  202.         Appends the given file to the composition, along with the proper mixer effect
  203.         """
  204.         self.debug("location:%s" % location)
  205.         start = self._lastposition
  206.         if self._lastadded:
  207.             start += self._levels[self._lastadded][2]
  208.             start -= self._levels[location][1]
  209.  
  210.         gnls = self._new_gnl_source(location, start)
  211.         self._composition.add(gnls)
  212.  
  213.         if self._lastadded:
  214.             # create the mixer
  215.             duration = self._levels[self._lastadded][3] - self._levels[self._lastadded][2] + self._levels[location][1]
  216.             mixer = self._new_mixer(start, duration)
  217.             self._composition.add(mixer)
  218.  
  219.         self._lastposition = start
  220.         self._lastadded = location
  221.  
  222.         self.debug("lastposition:%s , lastadded:%s" % (gst.TIME_ARGS(self._lastposition),
  223.                                                        self._lastadded))
  224.  
  225.     def _check_prerolled(self):
  226.         gst.debug("_check_prerolled: index: scan %d, play %d" % (
  227.             self._scani, self._playi))
  228.         if not self._prerolled and self._scani > self._playi + 1:
  229.             self._prerolled = True
  230.             # add initial sources here
  231.             self._append_file(self._files[0])
  232.             self._append_file(self._files[1])
  233.             self.debug("now prerolled and ready to play")
  234.             self.emit('prerolled')
  235.  
  236.  
  237.     def _emit_changed(self, file, when):
  238.         print "emitting changed for %s at %r" % (file, when)
  239.         self.emit('changed', file, when)
  240.  
  241.     def _source_clean(self, source):
  242.         source.set_state(gst.STATE_NULL)
  243.         self.remove(source)
  244.         source.clean()
  245.  
  246.     ## composition callbacks
  247.  
  248.     def _composition_pad_added_cb(self, comp, pad):
  249.         if self._srcpad:
  250.             return
  251.         self.debug("Ghosting source pad %s" % pad)
  252.         self._srcpad = gst.GhostPad("src", pad)
  253.         self._srcpad.set_active(True)
  254.         self.add_pad(self._srcpad)
  255.  
  256.     ## gst.Bin/Element virtual methods
  257.  
  258.     def do_handle_message(self, message):
  259.         self.debug("got message %s / %s / %r" % (message.src.get_name(), message.type.first_value_name, message))
  260.  
  261.         # chaining up
  262.         gst.Bin.do_handle_message(self, message)
  263.  
  264.     def do_state_change(self, transition):
  265.         if not self._prerolled:
  266.             gst.error("Call Jukebox.preroll() before!")
  267.             return gst.STATE_CHANGE_FAILURE
  268.         # chaining up
  269.         return gst.Bin.do_state_change(self, message)
  270.  
  271. gobject.type_register(Jukebox)
  272.  
  273. # helper functions
  274. def _find_elements_recurse(element):
  275.     if not isinstance(element, gst.Bin):
  276.         return [element, ]
  277.     l = []
  278.     for e in element.elements():
  279.         l.extend(_find_elements_recurse(e))
  280.     return l
  281.  
  282. def _find_unconnected_pad(bin, direction):
  283.     for e in _find_elements_recurse(bin):
  284.         for p in e.pads():
  285.             if p.get_direction() == direction and not p.get_peer():
  286.                 return p
  287.  
  288.     return None
  289.  
  290. # run us to test
  291. if __name__ == "__main__":
  292.     main = gobject.MainLoop()
  293.     pipeline = gst.Pipeline('jukebox')
  294.     list = open(sys.argv[1]).read().rstrip().split('\n')
  295.     print list
  296.     #source = Jukebox(list, random=True, loops=-1)
  297.     source = Jukebox(list, random=True, loops=1)
  298.  
  299.     def _jukebox_prerolled_cb(jukebox):
  300.         print "prerolled"
  301.         _start()
  302.  
  303.     def _jukebox_changed_cb(jukebox, filename, when):
  304.         print "changed file to %s at %s" % (filename, float(when) / gst.TIME_ARGS(gst.SECOND))
  305.  
  306.     def _jukebox_looped_cb(jukebox):
  307.         print "jukebox looped"
  308.  
  309.     def _start():
  310.         source.start()
  311.         print "setting pipeline to PLAYING"
  312.         pipeline.set_state(gst.STATE_PLAYING)
  313.         print "set pipeline to PLAYING"
  314.  
  315.     def jukebox_pad_added(comp, pad, sinkpad):
  316.         pad.link(sinkpad)
  317.  
  318.     def jukebox_message(bus, message):
  319.         if message.type == gst.MESSAGE_ERROR:
  320.             print "Error: %s" % message.parse_error()
  321.             main.quit()
  322.         elif message.type == gst.MESSAGE_EOS:
  323.             print "done"
  324.             main.quit()
  325.  
  326.     source.connect('prerolled', _jukebox_prerolled_cb)
  327.     source.connect('changed', _jukebox_changed_cb)
  328.     source.connect('looped', _jukebox_looped_cb)
  329.     source.preroll()
  330.     pipeline.add(source)
  331.  
  332.     bus = pipeline.get_bus()
  333.     bus.add_signal_watch()
  334.     bus.connect("message", jukebox_message)
  335.  
  336.     p = "alsasink"
  337.     if len(sys.argv) > 2:
  338.         p = " ".join(sys.argv[2:])
  339.  
  340.     print "parsing output pipeline %s" % p
  341.     sinkbin = gst.parse_launch("bin.( %s )" % p)
  342.     pipeline.add(sinkbin)
  343.     apad = _find_unconnected_pad(sinkbin, gst.PAD_SINK)
  344.     if not apad:
  345.         raise TypeError, "No unconnected sink pad found in bin %r" % sinkbin
  346.     sinkpad = gst.GhostPad("sink", apad)
  347.     sinkbin.add_pad(sinkpad)
  348.     source.connect('pad-added', jukebox_pad_added, sinkpad)
  349.  
  350.     print "Going into main loop"
  351.     sys.stdout.flush()
  352.     main.run()
  353.     print "Left main loop"
  354.     sys.stdout.flush()
  355.  
  356.     pipeline.set_state(gst.STATE_NULL)
  357.